package cn.sherer.beans.weixin;

import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import cn.sherer.utils.RequestUtils;
import com.alibaba.fastjson2.JSONObject;
import com.qq.weixin.mp.aes.AesException;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;

import static java.lang.String.valueOf;
import static cn.sherer.utils.StringUtils.*;
import static java.text.MessageFormat.format;
import static com.alibaba.fastjson2.JSONObject.*;
import static cn.sherer.utils.JsonUtils.toObject;

/**
 * 微信消息
 */
public class WxMsg {
    private String token = "";
    private String appid = "";
    private String encodingAESKey = "";

    private WxMsgResult resultData;
    private Map<String, Object> params = new HashMap<>();

    public WxMsg(HttpServletRequest request, String token) {
        this(request, token, "", "");
    }

    public WxMsg(HttpServletRequest request, String token, String appid, String encodingAESKey) {
        this(new HashMap<>(), token, appid, encodingAESKey);
        RequestUtils requestUtils = new RequestUtils(request);
        this.params.putAll(requestUtils.get());
        if (!requestUtils.getPostData().isEmpty()) {
            this.params.putAll(jsonToMap(requestUtils.getPostData()));
        }
    }

    public WxMsg(Map<String, Object> params, String token) {
        this(params, token, "", "");
    }

    public WxMsg(Map<String, Object> params, String token, String appid, String encodingAESKey) {
        this.token = token;
        this.appid = appid;
        this.encodingAESKey = encodingAESKey;
        this.params = params == null ? new HashMap<>() : params;
    }

    public String run(WxMsgOptions options) {
        // <editor-fold defaultstate="collapsed" desc="获取公共参数">
        String nonce = valueOf(get("nonce", "")); // 随机字符串
        String echostr = get("echostr", "").toString(); // 随机字符串（验证服务器时返回）
        String timestamp = valueOf(get("timestamp", "")); // 时间戳
        String signature = valueOf(get("signature", "")); // 签名
        String msg_signature = valueOf(get("msg_signature", "")); // 签名（安全模式时返回）
        Logger.getLogger(this.getClass().getName()).info(format("数据：{0}", toJSONString(params)));
        // </editor-fold>

        // <editor-fold defaultstate="collapsed" desc="验证签名">
        WXBizMsgCrypt crypt = null;
        if (isNull(echostr, "").length() > 0) {
            try {
                if (!sha1(true, this.token, timestamp, nonce).equals(signature)) return "签名错误";
            } catch (Exception e) {
                Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "签名错误", e);
                return "签名错误";
            }
            if (isNull(echostr, "").length() > 0) return echostr;
        }
        // </editor-fold>

        // <editor-fold defaultstate="collapsed" desc="判断消息是否解密">
        String encrypt = valueOf(get("Encrypt", ""));
        boolean isEncrypt = !isNull(msg_signature, "").equals("") && !encrypt.equals("");
        if (isEncrypt) {
            try {
                if (!sha1(true, this.token, timestamp, nonce, encrypt).equals(msg_signature)) return "签名错误";
            } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
                Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "签名错误", e);
                return "签名错误";
            }

            try {
                crypt = crypt == null ? new WXBizMsgCrypt(this.token, this.encodingAESKey, this.appid) : crypt;
                String decrypt  = crypt.decrypt(encrypt);
                Map<String, Object> decryptMap = jsonToMap(decrypt);
                decryptMap.forEach((t, u) -> params.put(t, u));
            } catch (AesException e) {
                Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "数据有误", e);
                return "数据有误";
            } catch (Exception e) {
                Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "解析失败", e);
                return "解析失败";
            }
        }
        // </editor-fold>

        // <editor-fold defaultstate="collapsed" desc="获取消息公共参数">
        String openId = valueOf(get("OpenId", "")); // 用户Openid
        String msgType = valueOf(get("MsgType", "")); // 消息类型，event
        String toUserName = valueOf(get("ToUserName", "")); // 开发者微信号
        String createTime = valueOf(get("CreateTime", "")); // 消息创建时间 （整型）
        String fromUserName = valueOf(get("FromUserName", "")); // 发送方帐号（一个OpenID）
        Logger.getLogger(this.getClass().getName()).info(format("数据：{0}", toJSONString(params)));
        this.resultData = new WxMsgResult(nonce, timestamp, isEncrypt, toUserName, openId, fromUserName, msgType, createTime); // 公共数据
        Object result = resultData; // 返回数据
        // </editor-fold>

        // <editor-fold defaultstate="collapsed" desc="处理消息">
        if (options != null) options.Msg((WxMsgResult) result, params);
        // </editor-fold>

        return "";
    }

    /**
     * 获取参数
     * @param key 参数名
     * @return 参数值
     */
    private Object get(String key) {
        return get(key, null);
    }

    /**
     * 获取参数
     * @param key 参数名
     * @param defaultValue 默认值
     * @return 参数值
     */
    private Object get(String key, Object defaultValue) {
        if (params != null && params.containsKey(key)) {
            return params.get(key);
        } else {
            return defaultValue;
        }
    }

    /**
     * JSON格式字符串转换为Map
     * @param strJSON JSON字符串
     * @return JSON数据转换后的Map
     */
    public static Map<String, Object> jsonToMap(String strJSON) {
        Map<String, Object> data = new HashMap<>();
        JSONObject json = toObject(strJSON);
        if (json == null || json.isEmpty()) { return data; }
        for (String key : json.keySet()) { data.put(key, json.get(key)); }
        return data;
    }

    /**
     * 获取成功结果
     * @return 成功结果
     */
    public static String getResultData() {
        return getResultData(0, "success");
    }

    /**
     * 获取结果
     * @param ErrCode 错误码
     * @param ErrMsg 错误信息
     * @return 结果
     */
    public static String getResultData(int ErrCode, String ErrMsg) {
        return new JSONObject() {{ put("ErrCode", ErrCode); put("ErrMsg", ErrMsg); }}.toString();
    }
}
